/*
PearReview - The peer review assistant.
Copyright (C) 2009  Dimitrij Pankratz, Anton Musichin
http://www.pearreview.com, info@pearreview.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.pearreview.scribeplugin;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListSelectionModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowFilter;
import javax.swing.RowSorter;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableRowSorter;

import com.pearreview.application.Messages;
import com.pearreview.scribeplugin.csv.*;
import com.pearreview.ui.CompletableJTextField;
import com.pearreview.ui.FormLabel;
import com.pearreview.ui.EscapeDialog;
import com.pearreview.ui.PearPanel;
import com.pearreview.ui.table.BooleanTableCellEditor;
import com.pearreview.ui.table.BooleanTableCellRenderer;
import com.pearreview.ui.table.IdItemTableCellRenderer;
import com.pearreview.ui.table.ListTableCellRenderer;
import com.pearreview.ui.table.SeverityMapTableCellRenderer;
import com.pearreview.ui.table.SeverityTableCellRenderer;
import com.pearreview.ui.table.DefaultAdvancedTableCellRenderer;
import com.pearreview.ui.table.ToolTipJTable;
import com.pearreview.util.CSVFileFilter;
import com.pearreview.util.csv.CSVTableModel;
import com.pearreview.util.csv.CSVWriter;
import com.pearreview.util.review.AbstractIdItem;
import com.pearreview.util.review.Finding;
import com.pearreview.util.review.IdComparator;
import com.pearreview.util.review.Severity;

/**
 * This class provides a dialog for managing the settings of the 'Trac' export.
 *
 */
public class TracSettingsDialog extends EscapeDialog {
	
	private static final long serialVersionUID = 4934578669725408193L;
	
	private static final String[] SEPARATOR_OPTIONS = {
		getLocalizedString("separator.comma"),
		getLocalizedString("separator.semicolon"),
		getLocalizedString("separator.colon"),
		getLocalizedString("separator.tabulator")
	};
	private static final Character[] SEPARATOR_OPTIONS_VALUES = {
		CSVWriter.COMMA_SEPARATOR,
		CSVWriter.SEMICOLON_SEPARATOR,
		CSVWriter.COLON_SEPARATOR,
		CSVWriter.TABULATOR_SEPARATOR
	};
	
	private PearPanel dialogPane;
	private JPanel contentPanel;
	private JLabel columnNameExportLabel;
	private JCheckBox columnNameExportCheck;
	private JLabel separatorLabel;
	private JComboBox separatorBox;
	private JScrollPane severityScroll;
	private JTable severityTable;
	private JScrollPane findingsScroll;
	private CSVTable findingsTable;
	private JLabel headingLabel;
	private JPanel buttonBar;
	private JButton okButton;
	private JButton cancelButton;
	
	private ButtonGroup buttonGroup;
	private JRadioButton idRadio;
	private JRadioButton descriptionRadio;
	private JRadioButton severityRadio;
	private JRadioButton referenceRadio;
	private JRadioButton aspectRadio;
	private JRadioButton imagesRadio;
	private JRadioButton customRadio;
	private CompletableJTextField fillColomnWith;
	private CompletableJTextField colomnName;
	private JButton addColomnBtn;
	
	private FormLabel colomnTypeLabel;
	private FormLabel colomnNameLabel;
	private FormLabel fillColomnLabel;
	
	private JButton deleteBtn;
	
	private Action removeAction = new RemoveAction();
	
	private TableRowSorter<FindingTableModel> findingSorter;
	
	private SeverityTableModel severityModel = new SeverityTableModel();
	private FindingTableModel findingModel = new FindingTableModel();
	
	public TracSettingsDialog(Frame owner) {
		super(owner);
		initComponents();
	}

	public TracSettingsDialog(Dialog owner) {
		super(owner);
		initComponents();
	}
	
	public TracSettingsDialog() {
		super();
		initComponents();
	}

	private void initComponents() {
		setTitle(getLocalizedString("title"));
		
		dialogPane = new PearPanel();
		contentPanel = new JPanel();
		columnNameExportLabel = new JLabel();
		columnNameExportCheck = new JCheckBox();
		separatorLabel = new JLabel();
		separatorBox = new JComboBox(SEPARATOR_OPTIONS);
		severityScroll = new JScrollPane();
		severityTable = new JTable();
		findingsScroll = new JScrollPane();
		findingsTable = new CSVTable();
		headingLabel = new JLabel();
		buttonBar = new JPanel();
		okButton = new JButton();
		cancelButton = new JButton();
		
		colomnTypeLabel = new FormLabel(getLocalizedString("column.type"));
		colomnNameLabel = new FormLabel(getLocalizedString("column.name"));
		fillColomnLabel = new FormLabel(getLocalizedString("column.fill"));
		
		deleteBtn = new JButton();
		
		idRadio = new JRadioButton("Id");
		descriptionRadio = new JRadioButton(Messages.getString(
				"PearReview.Scribe.findings.description"));
		severityRadio = new JRadioButton(Messages.getString(
				"PearReview.Scribe.findings.severity"));
		referenceRadio = new JRadioButton(Messages.getString(
				"PearReview.Scribe.findings.reference"));
		aspectRadio = new JRadioButton(Messages.getString(
				"PearReview.Scribe.findings.aspect"));
		imagesRadio = new JRadioButton(Messages.getString(
				"PearReview.Scribe.findings.image"));
		customRadio = new JRadioButton(getLocalizedString("own_colomn"));
		
		fillColomnWith = new CompletableJTextField();
		fillColomnWith.setEnabled(false);
		fillColomnWith.getDocument().addDocumentListener(
				new FillColumnListener());
		
		colomnName = new CompletableJTextField();
		colomnName.setEnabled(false);
		colomnName.getDocument().addDocumentListener(new NameListener());
		
		addColomnBtn = new JButton();
		

		//======== addColomnBtn ========
		addColomnBtn.setAction(new AddColumnAction());
		
		//======== fillColomnWith ========
		//fillColomnWith.setPreferredSize(new Dimension(300,20));
		
		//======== buttonGroup ========
		buttonGroup = new ButtonGroup();
		
		buttonGroup.add(idRadio);
		buttonGroup.add(descriptionRadio);
		buttonGroup.add(severityRadio);
		buttonGroup.add(referenceRadio);
		buttonGroup.add(aspectRadio);
		buttonGroup.add(imagesRadio);
		buttonGroup.add(customRadio);
		
		ActionListener al = new ColumnTypeChangeAction();
		
		idRadio.addActionListener(al);
		descriptionRadio.addActionListener(al);
		severityRadio.addActionListener(al);
		referenceRadio.addActionListener(al);
		aspectRadio.addActionListener(al);
		imagesRadio.addActionListener(al);
		customRadio.addActionListener(al);
		
		setEnabledRadio(false);
		
		customRadio.setSelected(true);
		
		//======== idRadio ========
		idRadio.setOpaque(false);
		idRadio.setFont(new Font("Tahoma", Font.PLAIN, 12));
		
		//======== descriptionRadio ========
		descriptionRadio.setOpaque(false);
		descriptionRadio.setFont(new Font("Tahoma", Font.PLAIN, 12));
		
		//======== severityRadio ========
		severityRadio.setOpaque(false);
		severityRadio.setFont(new Font("Tahoma", Font.PLAIN, 12));
		
		//======== referenceRadio ========
		referenceRadio.setOpaque(false);
		referenceRadio.setFont(new Font("Tahoma", Font.PLAIN, 12));
		
		//======== aspectRadio ========
		aspectRadio.setOpaque(false);
		aspectRadio.setFont(new Font("Tahoma", Font.PLAIN, 12));
		
		//======== imagesRadio ========
		imagesRadio.setOpaque(false);
		imagesRadio.setFont(new Font("Tahoma", Font.PLAIN, 12));
		
		//======== ownColomnRadio ========
		customRadio.setOpaque(false);
		customRadio.setFont(new Font("Tahoma", Font.PLAIN, 12));
		
		//======== addColomnBtn ========
		addColomnBtn.setFont(new Font("Tahoma", Font.PLAIN, 12));
		
		//======== deleteBtn ========
		deleteBtn.setFont(new Font("Tahoma", Font.PLAIN, 12));
		deleteBtn.setAction(removeAction);

		//======== this ========
		Container contentPane = getContentPane();
		contentPane.setLayout(new BorderLayout());

		//======== dialogPane ========
		{
			dialogPane.setBorder(new EmptyBorder(12, 12, 12, 12));
			dialogPane.setLayout(new BorderLayout());

			//======== contentPanel ========
			{
				contentPanel.setOpaque(false);

				//---- columnNameExportLabel ----
				columnNameExportLabel.setText(getLocalizedString(
						"columnNameExportLabel.text"));
				columnNameExportLabel.setFont(
						new Font("Tahoma", Font.PLAIN, 12));

				//---- separatorLabel ----
				separatorLabel.setText(getLocalizedString(
						"separatorLabel.text"));
				separatorLabel.setFont(new Font("Tahoma", Font.PLAIN, 12));

				//======== severityScroll ========
				{

					//---- severityTable ----
					severityTable.setAutoCreateRowSorter(true);
					severityTable.setFont(new Font("Tahoma", Font.PLAIN, 12));
					severityScroll.setViewportView(severityTable);
					severityTable.setModel(severityModel);
					
					SeverityTableCellRenderer severityRenderer = 
							new SeverityTableCellRenderer();
					severityTable.addMouseListener(severityRenderer);
					severityTable.addMouseMotionListener(severityRenderer);
					severityTable.setDefaultRenderer(Severity.class, 
							severityRenderer);
					
					DefaultAdvancedTableCellRenderer stringRenderer = 
							new DefaultAdvancedTableCellRenderer();
					severityTable.addMouseListener(stringRenderer);
					severityTable.addMouseMotionListener(stringRenderer);
					severityTable.setDefaultRenderer(String.class, 
							stringRenderer);
					
					BooleanTableCellRenderer booleanRenderer = 
							new BooleanTableCellRenderer();
					severityTable.addMouseListener(booleanRenderer);
					severityTable.addMouseMotionListener(booleanRenderer);
					severityTable.setDefaultRenderer(Boolean.class, 
							booleanRenderer);
					
					severityTable.setDefaultEditor(Boolean.class, 
							new BooleanTableCellEditor());
					
					severityTable.setSelectionBackground(new Color(0xbfd72b));
					
					severityModel.addTableModelListener(new SeverityListener());
					
					TableColumn checkColumn = 
							severityTable.getColumnModel().getColumn(0);
					checkColumn.setResizable(false);
					checkColumn.setMaxWidth(checkColumn.getPreferredWidth());
					severityTable.getTableHeader().setReorderingAllowed(false);
				}

				//======== findingsScroll ========
				{
					findingSorter = 
							new TableRowSorter<FindingTableModel>(findingModel);
					findingSorter.setSortsOnUpdates(true);
					findingsTable.setRowSorter(findingSorter);
					
					findingsTable.setModel(findingModel);
					
					findingsTable.setColumnSelectionAllowed(true);
					findingsTable.setRowSelectionAllowed(false);
					findingsTable.setSelectionMode(
							DefaultListSelectionModel.SINGLE_SELECTION);
					
					findingsTable.setSelectionBackground(new Color(0xbfd72b));
					
					ListSelectionListener lsl = new FindingSelectionListener();
					
					findingsTable.getSelectionModel().addListSelectionListener(
							lsl);
					findingsTable.getColumnModel().getSelectionModel()
							.addListSelectionListener(lsl);
					
					findingsScroll.setViewportView(findingsTable);
					
					ListTableCellRenderer listRenderer = 
							new ListTableCellRenderer();
					listRenderer.setColumnHighlightAllowed(true);
					listRenderer.setRowHighlightAllow(false);
					findingsTable.addMouseListener(listRenderer);
					findingsTable.addMouseMotionListener(listRenderer);
					findingsTable.setDefaultRenderer(List.class, listRenderer);
					
					Map<Severity, String> severityMap = 
							severityModel.getSeverityMap();
					SeverityMapTableCellRenderer severityMapRenderer = 
							new SeverityMapTableCellRenderer(severityMap);
					severityMapRenderer.setColumnHighlightAllowed(true);
					severityMapRenderer.setRowHighlightAllow(false);
					findingsTable.addMouseListener(severityMapRenderer);
					findingsTable.addMouseMotionListener(severityMapRenderer);
					findingsTable.setDefaultRenderer(Severity.class, 
							severityMapRenderer);
					
					IdItemTableCellRenderer idRenderer = 
							new IdItemTableCellRenderer();
					idRenderer.setColumnHighlightAllowed(true);
					idRenderer.setRowHighlightAllow(false);
					findingsTable.addMouseListener(idRenderer);
					findingsTable.addMouseMotionListener(idRenderer);
					findingsTable.setDefaultRenderer(AbstractIdItem.class, 
							idRenderer);
					
					DefaultAdvancedTableCellRenderer stringRenderer = 
							new DefaultAdvancedTableCellRenderer();
					stringRenderer.setColumnHighlightAllowed(true);
					stringRenderer.setRowHighlightAllow(false);
					findingsTable.addMouseListener(stringRenderer);
					findingsTable.addMouseMotionListener(stringRenderer);
					findingsTable.setDefaultRenderer(String.class, 
							stringRenderer);
					
					findingModel.addColumn(new FindingIdColumn());
					findingModel.addColumn(new FindingDescriptionColumn());
					findingModel.addColumn(
							new FindingSeverityColumn(severityMap));
					findingModel.addColumn(new FindingReferenceColumn());
					findingModel.addColumn(new FindingAspectColumn());
					findingModel.addColumn(
							new FindingExternalReferenceColumn());
					
					findingSorter.setComparator(0, new IdComparator());
				}


				//---- headingLabel ----
				headingLabel.setText(getLocalizedString("headingLabel.text"));
				headingLabel.setFont(new Font("Tahoma", Font.PLAIN, 14));
				
				columnNameExportCheck.setOpaque(false);
				columnNameExportCheck.setSelected(true);

				GroupLayout contentPanelLayout = new GroupLayout(contentPanel);
				contentPanel.setLayout(contentPanelLayout);
				contentPanelLayout.setHorizontalGroup(
					contentPanelLayout.createParallelGroup()
						.addGroup(contentPanelLayout.createSequentialGroup()
							.addGroup(contentPanelLayout.createParallelGroup()
								.addComponent(separatorLabel)
								.addComponent(columnNameExportLabel))
							.addGap(10)
							.addGroup(contentPanelLayout.createParallelGroup()
								.addComponent(separatorBox, 
										GroupLayout.PREFERRED_SIZE, 
										GroupLayout.PREFERRED_SIZE, 
										GroupLayout.PREFERRED_SIZE)
								.addComponent(columnNameExportCheck)))
						.addComponent(findingsScroll, 
								GroupLayout.Alignment.TRAILING, 
								GroupLayout.DEFAULT_SIZE, 
								550, 
								Short.MAX_VALUE)
						.addGroup(contentPanelLayout.createSequentialGroup()
								.addGroup(contentPanelLayout
										.createParallelGroup()
										.addComponent(colomnTypeLabel)
										.addGap(10)
										.addComponent(colomnNameLabel)
										.addGap(10)
										.addComponent(fillColomnLabel))
								.addGap(20)
								.addGroup(contentPanelLayout
										.createParallelGroup()
										.addGroup(contentPanelLayout
											.createSequentialGroup()
												.addComponent(idRadio)
												.addComponent(descriptionRadio)
												.addComponent(severityRadio)
												.addComponent(referenceRadio)
												.addComponent(aspectRadio)
												.addComponent(imagesRadio)
												.addComponent(customRadio))
										.addGap(10)
										.addComponent(colomnName)
										.addGap(10)
										.addComponent(fillColomnWith)
										.addGap(10)
										.addGroup(contentPanelLayout
											.createSequentialGroup()
												.addComponent(addColomnBtn)
												.addGap(10)
												.addComponent(deleteBtn))
										.addGap(10)
										))
						.addComponent(severityScroll, 
								GroupLayout.Alignment.TRAILING, 
								GroupLayout.DEFAULT_SIZE, 
								550, 
								Short.MAX_VALUE)
						.addGroup(contentPanelLayout.createSequentialGroup()
							.addComponent(headingLabel)
							.addContainerGap())
				);
				contentPanelLayout.setVerticalGroup(
					contentPanelLayout.createParallelGroup()
						.addGroup(contentPanelLayout.createSequentialGroup()
							.addComponent(headingLabel)
							.addGap(20, 20, 20)
							.addGroup(contentPanelLayout.createParallelGroup()
								.addGroup(contentPanelLayout
										.createSequentialGroup()
									.addComponent(columnNameExportLabel)
									.addGap(10, 10, 10)
									.addGroup(contentPanelLayout
											.createParallelGroup(
											GroupLayout.Alignment.BASELINE)
										.addComponent(separatorLabel)
										.addComponent(separatorBox, 
												GroupLayout.PREFERRED_SIZE, 
												GroupLayout.DEFAULT_SIZE, 
												GroupLayout.PREFERRED_SIZE)))
								.addComponent(columnNameExportCheck))
							.addGap(20, 20, 20)
							.addComponent(severityScroll, 
									GroupLayout.PREFERRED_SIZE, 
									150, 
									150)
							.addGap(30, 30, 30)
							.addGroup(contentPanelLayout.createParallelGroup()
									.addComponent(colomnTypeLabel,
											GroupLayout.PREFERRED_SIZE, 
											GroupLayout.PREFERRED_SIZE, 
											GroupLayout.PREFERRED_SIZE)
									.addGroup(contentPanelLayout
												.createParallelGroup()
												.addComponent(idRadio)
												.addComponent(descriptionRadio)
												.addComponent(severityRadio)
												.addComponent(referenceRadio)
												.addComponent(aspectRadio)
												.addComponent(imagesRadio)
												.addComponent(customRadio)))
							.addGap(10)
							.addGroup(contentPanelLayout.createParallelGroup()
									.addComponent(colomnNameLabel,
											GroupLayout.PREFERRED_SIZE, 
											GroupLayout.PREFERRED_SIZE, 
											GroupLayout.PREFERRED_SIZE)
									.addComponent(colomnName,
											GroupLayout.PREFERRED_SIZE, 
											GroupLayout.PREFERRED_SIZE, 
											GroupLayout.PREFERRED_SIZE))
							.addGap(10)
							.addGroup(contentPanelLayout.createParallelGroup()
									.addComponent(fillColomnLabel,
											GroupLayout.PREFERRED_SIZE, 
											GroupLayout.PREFERRED_SIZE, 
											GroupLayout.PREFERRED_SIZE)
									.addComponent(fillColomnWith,
											GroupLayout.PREFERRED_SIZE, 
											GroupLayout.PREFERRED_SIZE, 
											GroupLayout.PREFERRED_SIZE))
							.addGap(10)
							.addGroup(contentPanelLayout.createParallelGroup()
									.addComponent(addColomnBtn)
									.addGap(10)
									.addComponent(deleteBtn))
							.addGap(20)
							.addComponent(findingsScroll, 
									GroupLayout.PREFERRED_SIZE, 
									165, 
									Short.MAX_VALUE)
							.addGap(10))
				);
			}
			dialogPane.add(contentPanel, BorderLayout.CENTER);

			//======== buttonBar ========
			{
				buttonBar.setBorder(new EmptyBorder(20, 0, 0, 0));
				buttonBar.setLayout(new GridBagLayout());
				((GridBagLayout)buttonBar.getLayout()).columnWidths = 
					new int[] {0, 85, 80};
				((GridBagLayout)buttonBar.getLayout()).columnWeights = 
					new double[] {1.0, 0.0, 0.0};
				buttonBar.setOpaque(false);

				//---- okButton ----
				okButton.setText(getLocalizedString("export"));
				okButton.addActionListener(new ExportAction());
				
				buttonBar.add(okButton, 
						new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0,
					GridBagConstraints.CENTER, GridBagConstraints.BOTH,
					new Insets(0, 0, 0, 5), 0, 0));

				//---- cancelButton ----
				cancelButton.setText(getLocalizedString("cancel"));
				cancelButton.addActionListener(new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent e) {
						TracSettingsDialog.this.dispose();
					}
				});
				
				buttonBar.add(cancelButton, 
						new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0,
					GridBagConstraints.CENTER, GridBagConstraints.BOTH,
					new Insets(0, 0, 0, 0), 0, 0));
				
			}
			dialogPane.add(buttonBar, BorderLayout.SOUTH);
		}
		contentPane.add(dialogPane, BorderLayout.CENTER);
		pack();
		setMinimumSize(getSize());
		setLocationRelativeTo(getOwner());
	}
	
	private void setEnabledRadio(boolean enabled) {
		idRadio.setEnabled(enabled);
		descriptionRadio.setEnabled(enabled);
		severityRadio.setEnabled(enabled);
		referenceRadio.setEnabled(enabled);
		aspectRadio.setEnabled(enabled);
		imagesRadio.setEnabled(enabled);
		customRadio.setEnabled(enabled);
	}
	
	/**
	 * Returns localized string of this component
	 * @param key key of string to localize
	 * @return localized string of this component
	 */
	private static String getLocalizedString(String key) {
		return Messages.getString("PearReview.Scribe.trac_settings." + key);
	}
	
	public void setSeverities(List<Severity> severities) {
		boolean selectAll = severityModel.getSeverities() == null;
		List<Severity> selectedSeverities = 
				severityModel.getSelectedSeverities();
		severityModel.setSeverities(severities);
		
		if(selectAll)
			severityModel.selectAll();
		else
			severityModel.setSelectedSeverities(selectedSeverities);
	}
	
	public void setFindings(List<Finding> findings) {
		findingModel.setFindings(findings);
	}
	
	protected void modelColumnRemoved(int index) {
		List<? extends RowSorter.SortKey> sortKeys = 
				findingSorter.getSortKeys();
		
		List<RowSorter.SortKey> newSortKeys = 
				new ArrayList<RowSorter.SortKey>();
		
		if(sortKeys.size()> 0 && sortKeys.get(0).getColumn() != index) {
			for(RowSorter.SortKey sortKey: sortKeys) {
				if(sortKey.getColumn() == index)
					continue;
				
				int column = sortKey.getColumn();
				if(index < column)
					column--;
				
				sortKey = new RowSorter.SortKey(column, sortKey.getSortOrder());
				newSortKeys.add(sortKey);
			}
		}
		
		resetIdComparator();
		
		findingSorter.setSortKeys(newSortKeys);
	}
	
	protected void modelColumnInserted(int index) {
		modelColumnChanged(index);
	}
	
	protected void modelColumnChanged(int index) {
		List<? extends RowSorter.SortKey> sortKeys = 
				findingSorter.getSortKeys();
		
		resetIdComparator();
		
		findingSorter.setSortKeys(sortKeys);
		findingSorter.sort();
	}
	
	protected void resetIdComparator() {
		findingSorter.modelStructureChanged();
		
		int columns = findingModel.getColumnCount();
		for(int i = 0 ; i < columns ; i++) {
			FindingModelColumnRenderer column = findingModel.getColumn(i);
			if(column instanceof FindingIdColumn) {
				findingSorter.setComparator(i, new IdComparator());
			}
		}
	}
	
	private class ExportAction implements ActionListener, Serializable {
		private static final long serialVersionUID = 476782724307483013L;
		private File lastDir;
		@Override
		public void actionPerformed(ActionEvent ae) {
			TracSettingsDialog.this.setVisible(false);
			
			final JFileChooser saveDialog = new JFileChooser();
			saveDialog.setMultiSelectionEnabled(false);
			saveDialog.setAcceptAllFileFilterUsed(false);
			
			CSVFileFilter csvFilter = new CSVFileFilter();
			
			saveDialog.setFileFilter(csvFilter);
			saveDialog.setCurrentDirectory(lastDir);
			
			int ret = saveDialog.showSaveDialog(TracSettingsDialog.this);
			
			if (JFileChooser.APPROVE_OPTION != ret) {
				TracSettingsDialog.this.setVisible(true);
				return;
			}
			
			lastDir = saveDialog.getSelectedFile();
			if(!csvFilter.accept(lastDir)) {
				String fileName = lastDir.getName();
				fileName += CSVFileFilter.EXTENSION;
				lastDir = new File(lastDir.getParent(), fileName);
			}
			
			boolean columnsVisible = columnNameExportCheck.isSelected();
			int selectedSeparator = separatorBox.getSelectedIndex();
			
			CSVTableModel csvModel = findingsTable.getCSVModel(columnsVisible);
			char csvSeparator = SEPARATOR_OPTIONS_VALUES[selectedSeparator];
			
			
			CSVWriter csvWriter = new CSVWriter();
			try {
				csvWriter.write(lastDir, 
						csvModel, 
						csvSeparator);
			} catch (IOException e) {
				// TODO error message?!
				e.printStackTrace();
			}
		}
	}
	
	private class SeverityTableModel extends DefaultTableModel {
		private static final long serialVersionUID = 4447919064877823963L;
		private List<Severity> severities;
		private Map<Severity, String> severityMap = 
				new HashMap<Severity, String>();
		private Set<Integer> selectedIndizes = new HashSet<Integer>();
		
		private String[] columns = {
				null, 
				getLocalizedString("map.from"), 
				getLocalizedString("map.to")
		};
		
		public void setSeverities(List<Severity> severities) {
			severityMap.clear();
			this.severities = severities;
			fireTableDataChanged();
		}
		
		public List<Severity> getSeverities() {
			return severities;
		}
		
		public void selectAll() {
			for(int i = 0 ; i < getRowCount() ; i++) {
				selectedIndizes.add(i);
			}
			fireTableRowsUpdated(0, getRowCount() - 1);
		}
		
		public int getSelectedCount() {
			return selectedIndizes.size();
		}
		
		public List<Severity> getSelectedSeverities() {
			List<Severity> selectedMeetings = new ArrayList<Severity>();
			
			for(Integer index: selectedIndizes) {
				selectedMeetings.add(severities.get(index));
			}
			
			return selectedMeetings;
		}
		
		public void setSelectedSeverity(Severity selectedSeverity) {
			int index = severities.indexOf(selectedSeverity);
			if(index >= 0) {
				selectedIndizes.add(index);
				
				fireTableRowsUpdated(index, index);
			}
		}
		
		public void setSelectedSeverities(List<Severity> selectedSeverities) {
			for(Severity selectedSeverity: selectedSeverities) {
				setSelectedSeverity(selectedSeverity);
			}
		}
		
		public Map<Severity, String> getSeverityMap() {
			return severityMap;
		}
		
		@Override
		public int getRowCount() {
			if(severities == null)
				return 0;
			return severities.size();
		}
		
		@Override
		public int getColumnCount() {
			return columns.length;
		}
		
		@Override
		public String getColumnName(int column) {
			return columns[column];
		}
		
		@Override
		public Object getValueAt(int row, int column) {
			Severity severity = severities.get(row);
			switch(column) {
			case 0:
				return selectedIndizes.contains(row);
			case 1:
				return severity;
			case 2:
				return severityMap.containsKey(severity) ? 
						severityMap.get(severity) : severity.getName();
			default:
				return null;
			}
		}
		
		@Override
		public Class<?> getColumnClass(int columnIndex) {
			switch(columnIndex) {
			case 0:
				return Boolean.class;
			case 1:
				return Severity.class;
			case 2:
				return String.class;
			default:
				return super.getColumnClass(columnIndex);
			}
		}
		
		@Override
		public void setValueAt(Object aValue, int row, int column) {
			switch(column) {
			case 0:
				if(aValue.equals(true)) {
					selectedIndizes.add(row);
				} else {
					selectedIndizes.remove(row);
				}
				fireTableRowsUpdated(row, row);
				break;
			case 2:
				Severity severity = severities.get(row);
				severityMap.put(severity, aValue.toString());
				fireTableRowsUpdated(row, row);
				break;
			}
		}
		
		@Override
		public boolean isCellEditable(int row, int column) {
			return column == 0 || column == 2;
		}
	}
	
	private class SeverityListener implements TableModelListener, Serializable {
		private static final long serialVersionUID = -3994682207537203205L;
		private int lastSelectionCount = -1;
		@Override
		public void tableChanged(TableModelEvent e) {
			if(findingModel == null || findingModel.getRowCount() <= 0)
				return;
			
			findingModel.fireTableRowsUpdated(0, findingModel.getRowCount()-1);
			
			int count = severityModel.getSelectedCount();
			if(lastSelectionCount == count)
				return;
			
			lastSelectionCount = count;
			List<Severity> selected = severityModel.getSelectedSeverities();
			findingSorter.setRowFilter(new SeverityRowFilter(selected));
		}
	}
	
	private class SeverityRowFilter extends 
			RowFilter<FindingTableModel, Integer> implements Serializable {
		private static final long serialVersionUID = 592488179403131848L;
		private List<Severity> allowedSeverities;
		public SeverityRowFilter(List<Severity> allowedSeverities) {
			this.allowedSeverities = allowedSeverities;
		}
		@Override
		public boolean include(
				RowFilter.Entry<? extends FindingTableModel, 
						? extends Integer> entry) {
			FindingTableModel model = entry.getModel();
			Finding finding = model.getFinding(entry.getIdentifier());
			Severity severity = finding.getSeverity();
			return allowedSeverities.contains(severity);
		}
	}
	
	private class AddColumnAction extends AbstractAction {
		private static final long serialVersionUID = 6003331509807517510L;
		public AddColumnAction() {
			putValue(Action.NAME, getLocalizedString("add_column"));
		}
		@Override
		public void actionPerformed(ActionEvent ae) {
			FindingModelColumnRenderer renderer = new FindingCustomColumn();
			int columnIndex = findingModel.addColumn(renderer, false);
			modelColumnInserted(columnIndex);
			findingsTable.addColumn(new TableColumn(columnIndex));
			
			int selectIndex = findingsTable.getColumnCount() - 1;
			
			findingsTable.changeSelection(-1, selectIndex, false, false);
		}
	}
	
	private class RemoveAction extends AbstractAction {
		private static final long serialVersionUID = -3026809286031539650L;
		public RemoveAction() {
			this.putValue(Action.NAME, getLocalizedString("remove_column"));
			setEnabled(false);
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			int viewIndex = findingsTable.getSelectedColumn();
			if(viewIndex >= 0) {
				int columnIndex = findingsTable
						.convertColumnIndexToModel(viewIndex);
				TableColumn c = findingsTable
						.getColumnModel().getColumn(viewIndex);
				modelColumnRemoved(columnIndex);
				findingsTable.removeColumn(c);
				
				int columnCount = findingsTable.getColumnCount();	
				for(int i = 0 ; i < columnCount ; i++) {
					c = findingsTable.getColumnModel().getColumn(i);
					if(c.getModelIndex() > columnIndex)
						c.setModelIndex(c.getModelIndex() - 1);
				}
				
				findingModel.removeColumn(columnIndex, false);
			}
		}
	}
	
	private class FindingSelectionListener implements ListSelectionListener,
			Serializable{
		private int lastSelectedIndex = -1;
		private static final long serialVersionUID = 7017714537422986788L;
		@Override
		public void valueChanged(ListSelectionEvent e) {
			removeAction.setEnabled(findingsTable.getSelectedColumn() >= 0);
			
			int column = findingsTable.getSelectedColumn();
			int columnIndex = findingsTable.convertColumnIndexToModel(column);
			
			if(lastSelectedIndex == columnIndex)
				return;
			
			lastSelectedIndex = columnIndex;
				
			if(columnIndex < 0) {
				fillColomnWith.setText(null);
				fillColomnWith.setEnabled(false);
				
				colomnName.setText(null);
				colomnName.setEnabled(false);
				
				customRadio.setSelected(true);
				
				setEnabledRadio(false);
				return;
			}
			
			FindingModelColumnRenderer c = findingModel.getColumn(columnIndex);
			
			setEnabledRadio(true);
			
			colomnName.setText(c.getName());
			colomnName.setEnabled(true);
			
			if(c instanceof FindingIdColumn) {
				idRadio.setSelected(true);
			} else if(c instanceof FindingDescriptionColumn) {
				descriptionRadio.setSelected(true);
			} else if(c instanceof FindingSeverityColumn) {
				severityRadio.setSelected(true);
			} else if(c instanceof FindingReferenceColumn) {
				referenceRadio.setSelected(true);
			} else if(c instanceof FindingAspectColumn) {
				aspectRadio.setSelected(true);
			} else if(c instanceof FindingExternalReferenceColumn) {
				imagesRadio.setSelected(true);
			} else if(c instanceof FindingCustomColumn) {
				FindingCustomColumn fcc = (FindingCustomColumn)c;
				fillColomnWith.setText(fcc.getDefaultValue());
				customRadio.setSelected(true);
			}
			
			fillColomnWith.setEnabled(c instanceof FindingCustomColumn);
			if(!(c instanceof FindingCustomColumn))
				fillColomnWith.setText(null);
		}
	}
	
	private class CSVTable extends ToolTipJTable {
		private static final long serialVersionUID = 846876762183153666L;
		
		private class CSVModel implements CSVTableModel, Serializable {
			private static final long serialVersionUID = 6608550125280747059L;
			private boolean visible;
			public CSVModel(boolean columnsVisible) {
				visible = columnsVisible;
			}
			@Override
			public boolean areColumnsVisible() {
				return visible;
			}
			@Override
			public int getColumnCount() {
				return CSVTable.this.getColumnCount();
			}
			@Override
			public String getColumnName(int column) {
				int columnIndex = CSVTable.this
						.convertColumnIndexToModel(column);
				
				FindingTableModel model = 
						(FindingTableModel)CSVTable.this.getModel();
				
				return model.getColumn(columnIndex).getName();
			}
			@Override
			public int getRowCount() {
				return CSVTable.this.getRowCount();
			}
			@Override
			public String getValueAt(int row, int column) {
				FindingTableModel model = 
						(FindingTableModel)CSVTable.this.getModel();
				
				int columnIndex = 
						CSVTable.this.convertColumnIndexToModel(column);
				int rowIndex = CSVTable.this.getRowSorter()
						.convertRowIndexToModel(row);
				
				Finding finding = model.getFinding(rowIndex);
				
				FindingModelColumnRenderer renderer = 
						model.getColumn(columnIndex);
				
				return renderer.getStringValue(finding);
			}
		}

		public CSVTableModel getCSVModel(final boolean columnsVisible) {
			return new CSVModel(columnsVisible);
		}
	}
	
	private class ColumnTypeChangeAction implements ActionListener, 
			Serializable {
		private static final long serialVersionUID = -2385770555890035694L;
		@Override
		public void actionPerformed(ActionEvent ae) {
			FindingModelColumnRenderer newColumn;
			if(idRadio.isSelected())
				newColumn = new FindingIdColumn();
			else if(descriptionRadio.isSelected())
				newColumn = new FindingDescriptionColumn();
			else if(severityRadio.isSelected())
				newColumn = new FindingSeverityColumn(
						severityModel.getSeverityMap());
			else if(aspectRadio.isSelected())
				newColumn = new FindingAspectColumn();
			else if(referenceRadio.isSelected())
				newColumn = new FindingReferenceColumn();
			else if(imagesRadio.isSelected())
				newColumn = new FindingExternalReferenceColumn();
			else 
				newColumn = new FindingCustomColumn(fillColomnWith.getText());
			
			int column = findingsTable.getSelectedColumn();
			int columnIndex = findingsTable.convertColumnIndexToModel(column);
			
			fillColomnWith.setEnabled(newColumn instanceof FindingCustomColumn);
			if(!(newColumn instanceof FindingCustomColumn))
				fillColomnWith.setText(null);
			
			FindingModelColumnRenderer oldColumn= 
					findingModel.getColumn(columnIndex);
			
			if(oldColumn.getClass().equals(newColumn.getClass()))
				return;
			
			newColumn.setName(colomnName.getText());
			
			findingModel.removeColumn(columnIndex, false);
			findingModel.insertColumn(newColumn, columnIndex, false);
			
			modelColumnChanged(columnIndex);
			
			if(findingModel.getRowCount() > 0)
				findingModel.fireTableRowsUpdated(0, 
						findingModel.getRowCount() - 1);
			
			TableColumn tc = findingsTable.getColumnModel().getColumn(column);
			tc.setHeaderValue(newColumn.getColumnName());
			findingsTable.getTableHeader().repaint();
		}
	}
	
	private class NameListener implements DocumentListener, Serializable {
		private static final long serialVersionUID = -8108980956484235542L;
		@Override
		public void changedUpdate(DocumentEvent e) {
			changed();
		}
		@Override
		public void insertUpdate(DocumentEvent e) {
			changed();
		}
		@Override
		public void removeUpdate(DocumentEvent e) {
			changed();
		}
		protected void changed() {
			int column = findingsTable.getSelectedColumn();
			int columnIndex = findingsTable.convertColumnIndexToModel(column);
			
			if(columnIndex < 0)
				return;
			
			FindingModelColumnRenderer renderer = 
					findingModel.getColumn(columnIndex);
			
			renderer.setName(colomnName.getText());
			
			TableColumn tc;
			tc = findingsTable.getColumnModel().getColumn(column);
			tc.setHeaderValue(renderer.getColumnName());
			findingsTable.getTableHeader().repaint();
		}
	}
	
	private class FillColumnListener implements DocumentListener, Serializable {
		private static final long serialVersionUID = 6596664860922907852L;
		@Override
		public void changedUpdate(DocumentEvent e) {
			changed();
		}
		@Override
		public void insertUpdate(DocumentEvent e) {
			changed();
		}
		@Override
		public void removeUpdate(DocumentEvent e) {
			changed();
		}
		protected void changed() {
			int column = findingsTable.getSelectedColumn();
			int columnIndex = findingsTable.convertColumnIndexToModel(column);
			
			if(columnIndex < 0)
				return;
			
			FindingModelColumnRenderer renderer = 
					findingModel.getColumn(columnIndex);
			
			if(!(renderer instanceof FindingCustomColumn))
				return;
			
			FindingCustomColumn fcc = (FindingCustomColumn)renderer;
			
			fcc.setDefaultValue(fillColomnWith.getText());
			
			if(findingModel.getRowCount() > 0)
				findingModel.fireTableRowsUpdated(0, 
						findingModel.getRowCount() - 1);
		}
	}
}
